/*
    http://bhepple.freeshell.org

    Copyright (C) 2010 Bob Hepple <bhepple@freeshell.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/

/**
 * \file tba_test.c unit tests for tba.c
 *
 * To run from the source directory:
 *
 * libtool --mode=execute valgrind --leak-check=full --show-reachable=yes ./tests/tba_test
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tba.h>
#include "./tb_expect.h"

void *xcalloc (size_t nmemb, size_t size)
{
    void *retval = calloc(nmemb, size);

    /* Normally (ie as specified on GNU pages), xcalloc etc do not
     * return when the heap is exhausted. tba does not make that
     * assumption and provides code for the heap exhaustion case.
     *
     * For example, a longjmp could be used here to take us to a
     * recovery routine ie a routine that has previously called
     * setjmp(). It could free some stuff (maybe a buffer alloc'd as a
     * safety reserve) and then either exit safely or try to continue,
     * perhaps with a warning.
     *
     * extern jmp_buf               NoMoreHeap; 
     * if (!retval) longjmp(NoMoreHeap, 1); */

    return(retval);
}

void *xmalloc (size_t size)
{
    void *retval = malloc(size);
    /* if (!retval) longjmp(NoMoreHeap, 1); */
    return(retval);
}

void *xrealloc(void *oldbuf, size_t size)
{
    void *retval = NULL;

    retval = realloc(oldbuf, size);

    /* if (!retval) longjmp(NoMoreHeap, 1); */
    return(retval);
}

void xfree(void *buf)
{
    free(buf);
}

void printfArray(char *label, tba  s)
{
    int len = 0;

    if (s) {
        tba p = s;
        for (; *p; p++) len++;
        printf("%s: length = %d, maxlen = %d, curlen = %d\n", label,
               len, tba_get_maxlen(s), tba_get_len(s));
    }
}

int errors = 0;
int chunksize = 1;

void header(char *s, int line)
{
    static int last_error = 0;
    if (errors == last_error) {
        printf("ok\n");
    } else {
        last_error = errors;
    }

    printf("*******************************\nline %d: %s:\n", line, s);
}

#define PRINT_CLUES                                                     \
    printfArray("a", a);                                                \
    printf("i = %d chunksize = %d width = %d\n", i, chunksize, width);  \
    printf("callbacks = (%p,%p,%p)\n", free_callback,                   \
           create_callback, copy_callback);                             \
    errors++;

#define CHECK_ARRAY(a,s)                                            \
    for (i = 0; i < s; i++) {                                       \
        if (create_callback) {                                      \
            bEXPECT(check_payload((struct payload_s *)a[i]));       \
        }                                                           \
    }                                                               \
    npEXPECT(a[s]);

#define TEST_STRING "Hello world"
#define SENTINEL1 0xdeadbeef
#define SENTINEL2 0xbeefdead
#define ORIG_SIZE 500
#define ORIG_WIDTH 20
#define NEW_SIZE 1000
#define COPY_OFFSET 4000

struct payload_s {
    int sentinel1;
    char *text;
    int count;
    int sentinel2;
};

/* constant width payload */

/* callback frees anything pointed to in payload and returns payload */
static void *f_free_callback(void *payload, size_t index, void *caller_data)
{
    struct payload_s *pl = (struct payload_s *) payload;

    if (pl && pl->text) xfree(pl->text);
    return(payload);
}

/* assumes tba has done malloc - don't check so we get an abort on
 * first error! */
static void *f_create_callback(void *payload, size_t index, void *caller_data)
{
    void *retval = NULL;
    struct payload_s *pl = (struct payload_s *) payload;

    retval = payload;
    pl->sentinel1 = SENTINEL1;
    pl->text = strdup(TEST_STRING);
    pl->count = index;
    pl->sentinel2 = SENTINEL2;
    return(retval);
}

/* assumes tba has done malloc */
static void *f_copy_callback(void *dest, void *source, size_t index, 
                           void *caller_data)
{
    struct payload_s *dpl = NULL;
    struct payload_s *spl = NULL;
    void *retval = NULL;

    dpl = (struct payload_s *) dest;
    spl = (struct payload_s *) source;

    retval = dest;
    dpl->sentinel1 = spl->sentinel1;
    dpl->text = strdup(spl->text);
    dpl->count = spl->count + COPY_OFFSET;
    dpl->sentinel2 = spl->sentinel2;

    return(retval);
}

/* variable length payloads */

/* callback does free and returns NULL */
static void *v_free_callback(void *payload, size_t index, void *caller_data)
{
    struct payload_s *pl = (struct payload_s *) payload;

    if (pl && pl->text) xfree(pl->text);
    if (payload) xfree(payload);
    return(NULL);
}

/* callback does malloc because tba doesn't know the width */
static void *v_create_callback(void *payload, size_t index, void *caller_data)
{
    void *retval = NULL;
    struct payload_s *pl = NULL;

    pl = xmalloc(sizeof(struct payload_s) + index);
    retval = pl;

    if (pl) {
        pl->sentinel1 = SENTINEL1;
        pl->text = strdup(TEST_STRING);
        pl->count = index;
        pl->sentinel2 = SENTINEL2;
    }
    return(retval);
}

/* calls create_callback */
static void *v_copy_callback(void *dest, void *source, size_t index, 
                             void *caller_data)
{
    void *retval = NULL;

    if (!source) goto end;

    retval = v_create_callback(dest, index, caller_data);
    if (retval) {
        struct payload_s *dpl;
        struct payload_s *spl;

        dest = retval;
        dpl = (struct payload_s *) dest;
        spl = (struct payload_s *) source;
        if (dpl->text) xfree(dpl->text);
        dpl->sentinel1 = spl->sentinel1;
        dpl->text = strdup(spl->text);
        dpl->count = spl->count + 4000;
        dpl->sentinel2 = spl->sentinel2;
    }

end:
    return(retval);
}

int check_payload(struct payload_s *pl)
{
    int retval = 0;

    if (!pl) {
        printf("\nerror: NULL payload\n");
        goto end;
    }
    if (pl->sentinel1 != SENTINEL1) {
        printf("\nerror: bad sentinel1 in payload\n");
        goto end;
    }
    if (!pl->text) {
        printf("\nerror: NULL text in payload\n");
        goto end;
    }
    if (strcmp(pl->text, TEST_STRING)) {
        printf("\nerror: bad text in payload\n");
        goto end;
    }

    if (pl->sentinel2 != SENTINEL2) {
        printf("\nerror: bad sentinel2 in payload\n");
        goto end;
    }
    retval = 1;

end:
    return(retval);
}

tba new_array(int                   size,
              int                   width, 
              tba_free_callback_t   free_callback, 
              tba_create_callback_t create_callback, 
              tba_copy_callback_t   copy_callback,
              void *                data)
{
    tba retval = NULL;
    int i;

    retval = tba_new(size, width, free_callback, create_callback, 
                         copy_callback, data);
    if (retval) {
        if (width == 0) {
            // variable width
            if (create_callback == NULL) {
                // we need to malloc and format the payload as the
                // callbacks have not done it
                for (i = 0; i < size; i++) {
                    retval[i] = v_create_callback(NULL, i, data);
                }
            }
        } else {
            // fixed width
            if (create_callback == NULL) {
                for (i = 0; i < size; i++) {
                    // we need to format the payload
                    f_create_callback(retval[i], i, data);
                }
            }
        }
    }
    return(retval);
}

void free_array(tba *                 a,
                int                   width, 
                tba_free_callback_t   free_callback, 
                tba_create_callback_t create_callback, 
                tba_copy_callback_t   copy_callback,
                void *                data)
{
    int i, len;
    
    if (*a && !width && !free_callback) {
        len = tba_get_len(*a);
        for (i = 0; i < len; i++) {
            if ((*a)[i]) v_free_callback((*a)[i], i, data);
            (*a)[i] = NULL;
        }
    }
    tba_free(a);
}

int arraytest(int width, 
              tba_free_callback_t free_callback, 
              tba_create_callback_t create_callback, 
              tba_copy_callback_t copy_callback)
{
    int errors = 0;
    tba a = NULL, b = NULL;
    int value;
    int i = 0;
    void *p = NULL;
    int i1 = COPY_OFFSET;
    int i2 = i1 + 1;
    int i3 = i2 + 1;
    int count = 0;

    printf("\narraytest(%d): width = %d callbacks = (%p,%p,%p)\n", 
           chunksize, width, free_callback, create_callback, 
           copy_callback);

    header("tba_new/free", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    CHECK_ARRAY(a, ORIG_SIZE);
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_get_maxlen", __LINE__);
    iEXPECT(tba_get_maxlen(NULL), 0);

    header("tba_resize", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    pEXPECT(tba_resize(&a, NEW_SIZE));
    niEXPECT(NEW_SIZE - tba_get_maxlen(a));
    CHECK_ARRAY(a, NEW_SIZE);
    pEXPECT(tba_resize(&a, ORIG_SIZE));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    CHECK_ARRAY(a, ORIG_SIZE);
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_insert - insert at the start", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    pEXPECT(tba_insert(&a, 0, NEW_SIZE - ORIG_SIZE));
    niEXPECT(NEW_SIZE - tba_get_maxlen(a));
    CHECK_ARRAY(a, NEW_SIZE);
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_insert - insert near start", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    pEXPECT(tba_insert(&a, 2, NEW_SIZE - ORIG_SIZE));
    niEXPECT(NEW_SIZE - tba_get_maxlen(a));
    CHECK_ARRAY(a, NEW_SIZE);
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_insert - insert at the end", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    pEXPECT(tba_insert(&a, -1, NEW_SIZE - ORIG_SIZE));
    niEXPECT(NEW_SIZE - tba_get_maxlen(a));
    CHECK_ARRAY(a, NEW_SIZE);
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_insert - insert near the end", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    pEXPECT(tba_insert(&a, ORIG_SIZE - 2, NEW_SIZE - ORIG_SIZE));
    niEXPECT(NEW_SIZE - tba_get_maxlen(a));
    CHECK_ARRAY(a, NEW_SIZE);
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_insert - NULL array", __LINE__);
    npEXPECT(tba_insert(&a, 2, NEW_SIZE - ORIG_SIZE));

    header("tba_delete - delete at the end", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    if ((width == 0) && !free_callback) {
        v_free_callback(a[ORIG_SIZE - 1], ORIG_SIZE - 1, &count);
    }
    pEXPECT(tba_delete(a, -1, 1));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    CHECK_ARRAY(a, ORIG_SIZE - 1);
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_delete - delete at the start", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    if ((width == 0) && !free_callback) {
        v_free_callback(a[0], 0, &count);
    }
    pEXPECT(tba_delete(a, 0, 1));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    CHECK_ARRAY(a, ORIG_SIZE - 1);
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_delete - delete in the middle", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    if ((width == 0) && !free_callback) {
        v_free_callback(a[2], 2, &count);
    }
    pEXPECT(tba_delete(a, 2, 1));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    CHECK_ARRAY(a, ORIG_SIZE - 1);
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_delete - delete lots in the middle", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    if ((width == 0) && !free_callback) {
        for (i = 2; i < ORIG_SIZE; i++) v_free_callback(a[i], i, &count);
    }
    pEXPECT(tba_delete(a, 2, ORIG_SIZE * 2));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    npEXPECT(a[ORIG_SIZE]);
    CHECK_ARRAY(a, 2);
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_delete - delete lots from the end", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    if ((width == 0) && !free_callback) {
        v_free_callback(a[ORIG_SIZE - 1], ORIG_SIZE, &count);
    }
    pEXPECT(tba_delete(a, -1, ORIG_SIZE * 2));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(a));
    CHECK_ARRAY(a, ORIG_SIZE - 1);
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_copy", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    pEXPECT(b = tba_copy(a));
    niEXPECT(ORIG_SIZE - tba_get_maxlen(b));
    CHECK_ARRAY(b, ORIG_SIZE);
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);
    free_array(&b, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_push", __LINE__);
    pEXPECT(a = tba_push(NULL, &i1));
    pEXPECT(tba_push(&a, &i2));
    pEXPECT(tba_push(&a, &i3));
    iEXPECT(tba_get_len(a), 3);
    header("tba_get_last", __LINE__);
    pEXPECT(p = tba_get_last(a));
    iEXPECT(*(int *)p, COPY_OFFSET + 2);
    header("tba_get_first", __LINE__);
    pEXPECT(p = tba_get_first(a));
    iEXPECT(*(int *)p, COPY_OFFSET);
    header("tba_pop_last", __LINE__);
    pEXPECT(p = tba_pop_last(a));
    iEXPECT(tba_get_len(a), 2);
    iEXPECT(*(int *)p, COPY_OFFSET + 2);
    header("tba_pop_first", __LINE__);
    pEXPECT(p = tba_pop_first(a));
    iEXPECT(tba_get_len(a), 1);
    iEXPECT(*(int *)p, COPY_OFFSET);
    pEXPECT(p = tba_pop_first(a));
    iEXPECT(tba_get_len(a), 0);
    iEXPECT(*(int *)p, COPY_OFFSET + 1);
    npEXPECT(p = tba_pop_first(a));
    tba_free(&a);

    header("tba_push with arbitrary array types", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    p = NULL;
    if (!width && !create_callback) {
        p = v_create_callback(p, NEW_SIZE * 2, &count);
    } else {
        if (width) {
            p = xmalloc(width);
        }
        if (create_callback) {
            p = (*create_callback)(p, NEW_SIZE * 2, &count);
        }
    }
    pEXPECT(tba_push(&a, p));
    pEXPECT(p = tba_get_first(a));
    if (create_callback) {
        bEXPECT(check_payload((struct payload_s *)p));
    }
    pEXPECT(p = tba_get_last(a));
    if (create_callback) {
        bEXPECT(check_payload((struct payload_s *)p));
    }
    pEXPECT(p = tba_pop_first(a));
    if (create_callback) {
        bEXPECT(check_payload((struct payload_s *)p));
    }
    if (!width && !create_callback) {
        v_free_callback(p, 0, &count);
    } else {
        if (free_callback) p = (*free_callback)(p, 0, &count);
        if (p) xfree(p);
    }
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_put_index/get - NULL destination creates an unmanaged array", 
           __LINE__);
    npEXPECT(tba_put_index(NULL, 1, NULL));
    pEXPECT(tba_put_index(&a, 0, &a));
    npEXPECT(tba_put_index(&a, -1, &a));
    iEXPECT(tba_get_len(a), 1);
    npEXPECT(tba_get_index(a, 0) - (void *)&a);
    header("tba_put_index/get - put beyond the end of the array", __LINE__);
    pEXPECT(a = tba_put_index(&a, NEW_SIZE - 1, &a));
    iEXPECT(tba_get_len(a), NEW_SIZE);
    for (i = 0; i < NEW_SIZE; i++) npEXPECT(tba_get_index(a, i) - a[i]);
    pEXPECT(b = tba_copy(a));
    for (i = 0; i < NEW_SIZE; i++) pEXPECT(b = tba_put_index(&b, i, a[i]));
    iEXPECT(tba_get_len(b), NEW_SIZE);
    for (i = 0; i < NEW_SIZE; i++) npEXPECT(tba_get_index(b, i) - a[i]);
    tba_free(&a);
    tba_free(&b);

    header("tba_put_index/get to an illegal position", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    npEXPECT(tba_put_index(&a, -1, &i));
    iEXPECT(tba_get_len(a), ORIG_SIZE);
    CHECK_ARRAY(a, ORIG_SIZE);
    for (i = 0; i < ORIG_SIZE; i++) pEXPECT(tba_get_index(a, i));
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_put_index/get - put NULL beyond the end of the array", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    pEXPECT(tba_put_index(&a, NEW_SIZE * 2, NULL));
    iEXPECT(tba_get_len(a), ORIG_SIZE);
    CHECK_ARRAY(a, ORIG_SIZE);
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_put_index/get - put NULL at the start of the array", __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    if (!width && !create_callback) {
        v_free_callback(a[0], 0, &count);
    }
    pEXPECT(tba_put_index(&a, 0, NULL));
    iEXPECT(tba_get_len(a), ORIG_SIZE);
    npEXPECT(tba_get_index(a, 0));
    for (i = 1; i < ORIG_SIZE; i++) {
        if (create_callback) {
            bEXPECT(check_payload((struct payload_s *)tba_get_index(a, i)));
        } else {
            pEXPECT(tba_get_index(a, i));
        }
    }
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);

    header("tba_put_index/get - put non-NULL beyond the end of the array", 
           __LINE__);
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));
    p = NULL;
    if (!width && !create_callback) {
        p = v_create_callback(p, NEW_SIZE * 2, &count);
    } else {
        if (width) {
            p = xmalloc(width);
        }
        if (create_callback) {
            p = (*create_callback)(p, NEW_SIZE * 2, &count);
        }
    }
    pEXPECT(tba_put_index(&a, NEW_SIZE * 2, p));
    iEXPECT(tba_get_len(a), NEW_SIZE * 2 + 1);
    for (i = 0; i < ORIG_SIZE; i++) {
        if (create_callback) {
            bEXPECT(check_payload((struct payload_s *)tba_get_index(a, i)));
        } else {
            pEXPECT(tba_get_index(a, i));
        }
    }
    for (i = ORIG_SIZE; i < NEW_SIZE * 2; i++) {
        if (create_callback) {
            bEXPECT(check_payload((struct payload_s *)tba_get_index(a, i)));
        } else if (width) {
            pEXPECT(tba_get_index(a, i));
        } else {
            npEXPECT(tba_get_index(a, i));
        }
    }
    if (create_callback) {
        bEXPECT(check_payload((struct payload_s *)tba_get_index(a, i)));
    } else {
        pEXPECT(tba_get_index(a, i));
    }

    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);
    
    header("tba_cat", __LINE__);
    npEXPECT(tba_cat(NULL, NULL));
    pEXPECT(a = new_array(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &count));

    npEXPECT(tba_cat(NULL, a));
    CHECK_ARRAY(a, ORIG_SIZE);
    pEXPECT(tba_cat(&b, a));
    CHECK_ARRAY(b, ORIG_SIZE);
    pEXPECT(tba_cat(&b, a));
    CHECK_ARRAY(b, ORIG_SIZE * 2);
    pEXPECT(tba_cat(&a, a));
    CHECK_ARRAY(a, ORIG_SIZE * 2);
    free_array(&a, width, free_callback, create_callback, 
               copy_callback, &count);
    free_array(&b, width, free_callback, create_callback, 
               copy_callback, &count);

    header("arraytest finished", __LINE__);
    printf("arraytest(%d): err_count = %d\n", chunksize, errors);

end:
    return(errors);
}

int unittest(int chunksize_in)
{
    chunksize = chunksize_in;

    printf("\nRunning unittest with chunksize = %d\n", chunksize);

    tba_set_chunksize(chunksize);

    errors += arraytest(ORIG_WIDTH, NULL, NULL, NULL);
    errors += arraytest(ORIG_WIDTH, f_free_callback, f_create_callback, 
                        f_copy_callback);
    errors += arraytest(0, NULL, NULL, NULL);
    errors += arraytest(0, v_free_callback, v_create_callback, 
                        v_copy_callback);
    return(errors);
}

int main(int argc, char **argv)
{
    int errors = 0;
#if 0
    int value;
    tba a = NULL;
    tba b = NULL;
    tba c = NULL;
#endif

    setenv("TBARRAY_CHECK", "1", 2);

    errors += unittest(1);
    errors += unittest(23);
    errors += unittest(128);
    errors += unittest(4096);
    errors += unittest(40960);

#if 0
    header("tba_check", __LINE__);
    pEXPECT(a = tba_new_str("asd"));
    memcpy(a, "mmm", 4); /* overwrite the first sentinel */
    npEXPECT(b = tba_strdup(a));
    free(a); a = NULL;
    free(b); b = NULL;

    {
        size_t physize;
        size_t *sentinel2;

        pEXPECT(a = tba_new_str("asd"));
        physize = ((a->maxlen + 1) / sizeof(size_t)) + 4;
        sentinel2 = (size_t *) a + physize - 1;

        memcpy(sentinel2, "mmm", 4); /* overwrite the second sentinel */
    }
    npEXPECT(b = tba_strdup(a));
    iEXPECT(tba_get_maxlen(a), -1);
    free(a); a = NULL;
    free(b); b = NULL;
#endif

    printf("\nTotal: %d error%c\n", errors, errors == 1? ' ': 's');
    return(errors);
}

